#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// http://github.com/jskeet/dotnet-protobufs/
// Original C++/Java/Python code:
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
#if !LITE
using Google.ProtocolBuffers.Collections;
using Google.ProtocolBuffers.Descriptors;
#endif

namespace Google.ProtocolBuffers {
  /// <summary>
  /// TODO(jonskeet): Write summary text.
  /// </summary>
  public sealed class UninitializedMessageException : Exception {

    private readonly IList<string> missingFields;

    private UninitializedMessageException(IList<string> missingFields)
        : base(BuildDescription(missingFields)) {
      this.missingFields = new List<string>(missingFields);
    }
    /// <summary>
    /// Returns a read-only list of human-readable names of
    /// required fields missing from this message. Each name
    /// is a full path to a field, e.g. "foo.bar[5].baz"
    /// </summary>
    public IList<string> MissingFields {
      get { return missingFields; }
    }

    /// <summary>
    /// Converts this exception into an InvalidProtocolBufferException.
    /// When a parsed message is missing required fields, this should be thrown
    /// instead of UninitializedMessageException.
    /// </summary>
    public InvalidProtocolBufferException AsInvalidProtocolBufferException() {
      return new InvalidProtocolBufferException(Message);
    }

    /// <summary>
    /// Constructs the description string for a given list of missing fields.
    /// </summary>
    private static string BuildDescription(IEnumerable<string> missingFields) {
      StringBuilder description = new StringBuilder("Message missing required fields: ");
      bool first = true;
      foreach(string field in missingFields) {
        if (first) {
          first = false;
        } else {
          description.Append(", ");
        }
        description.Append(field);
      }
      return description.ToString();
    }

    /// <summary>
    /// For Lite exceptions that do not known how to enumerate missing fields
    /// </summary>
    public UninitializedMessageException(IMessageLite message)
      : base(String.Format("Message {0} is missing required fields", message.GetType())) {
      missingFields = new List<string>();
    }

#if !LITE
    public UninitializedMessageException(IMessage message)
        : this(FindMissingFields(message)) {
    }

    /// <summary>
    /// Returns a list of the full "paths" of missing required
    /// fields in the specified message.
    /// </summary>
    private static IList<String> FindMissingFields(IMessage message) {
      List<String> results = new List<String>();
      FindMissingFields(message, "", results);
      return results;
    }

    /// <summary>
    /// Recursive helper implementing FindMissingFields.
    /// </summary>
    private static void FindMissingFields(IMessage message, String prefix, List<String> results) {
      foreach (FieldDescriptor field in message.DescriptorForType.Fields) {
        if (field.IsRequired && !message.HasField(field)) {
          results.Add(prefix + field.Name);
        }
      }

      foreach (KeyValuePair<FieldDescriptor, object> entry in message.AllFields) {
        FieldDescriptor field = entry.Key;
        object value = entry.Value;

        if (field.MappedType == MappedType.Message) {
          if (field.IsRepeated) {
            int i = 0;
            foreach (object element in (IEnumerable) value) {
              if (element is IMessage) {
                FindMissingFields((IMessage)element, SubMessagePrefix(prefix, field, i++), results);
              } else {
                results.Add(prefix + field.Name);
              }
            }
          } else {
            if (message.HasField(field)) {
              if (value is IMessage) {
                FindMissingFields((IMessage)value, SubMessagePrefix(prefix, field, -1), results);
              } else {
                results.Add(prefix + field.Name);
              }
            }
          }
        }
      }
    }

    private static String SubMessagePrefix(String prefix, FieldDescriptor field, int index) {
      StringBuilder result = new StringBuilder(prefix);
      if (field.IsExtension) {
        result.Append('(')
              .Append(field.FullName)
              .Append(')');
      } else {
        result.Append(field.Name);
      }
      if (index != -1) {
        result.Append('[')
              .Append(index)
              .Append(']');
      }
      result.Append('.');
      return result.ToString();
    }
#endif
  }
}
